Previous

1.20 合并多个字典或映射

问题

现在有多个字典或者映射,你想将它们从逻辑上合并为一个单一的映射后执行某些操作, 比如查找值或者检查某些键是否存在。

解决方案

假如你有如下两个字典:


In [1]:
a = {"x" : 1, "z" : 3}
b = {"y" : 2, "z" : 4}

现在假设你必须在两个字典中执行查找操作(比如先从 a 中找,如果找不到再在 b 中找)。 一个非常简单的解决方案就是使用 collections 模块中的 ChainMap 类。比如:


In [2]:
from collections import ChainMap
c = ChainMap(a, b)
print(c["x"])


1

In [3]:
print(c["y"])


2

In [4]:
print(c["z"])


3

讨论

一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。 然后,这些字典并不是真的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表 并重新定义了一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的,比如:


In [5]:
len(c)


Out[5]:
3

In [6]:
list(c.keys())


Out[6]:
['x', 'z', 'y']

In [7]:
list(c.values())


Out[7]:
[1, 3, 2]

如果出现重复键,那么第一次出现的映射值会被返回。 因此,例子程序中的 c['z'] 总是会返回字典 a 中对应的值,而不是 b 中对应的值。

对于字典的更新或删除操作总是影响的是列表中第一个字典。比如:


In [8]:
c["z"] = 10
c["w"] = 40

In [9]:
del c["x"]

In [10]:
a


Out[10]:
{'w': 40, 'z': 10}

In [11]:
del c["y"]


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
C:\Program Files\Anaconda3\lib\collections\__init__.py in __delitem__(self, key)
    911         try:
--> 912             del self.maps[0][key]
    913         except KeyError:

KeyError: 'y'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-11-41472f861647> in <module>()
----> 1 del c["y"]

C:\Program Files\Anaconda3\lib\collections\__init__.py in __delitem__(self, key)
    912             del self.maps[0][key]
    913         except KeyError:
--> 914             raise KeyError('Key not found in the first mapping: {!r}'.format(key))
    915 
    916     def popitem(self):

KeyError: "Key not found in the first mapping: 'y'"

ChainMap 对于编程语言中的作用范围变量(比如 globals , locals 等)是非常有用的。 事实上,有一些方法可以使它变得简单:


In [12]:
values = ChainMap()
values["x"] = 1

In [13]:
# Add a new mapping
values = values.new_child()

In [14]:
values["x"] = 2

In [15]:
# Add a new mapping
values = values.new_child()
values["x"] = 3

In [16]:
values


Out[16]:
ChainMap({'x': 3}, {'x': 2}, {'x': 1})

In [17]:
values["x"]


Out[17]:
3

In [18]:
# Discard last mapping
values = values.parents
values["x"]


Out[18]:
2

In [19]:
values = values.parents
values["x"]


Out[19]:
1

In [20]:
values


Out[20]:
ChainMap({'x': 1})

作为 ChainMap 的替代,你可能会考虑使用 update() 方法将两个字典合并。比如:


In [21]:
a = {"x" : 1, "z" : 3}
b = {"y" : 2, "z" : 4}
merged = dict(b)
merged


Out[21]:
{'y': 2, 'z': 4}

In [22]:
merged.update(a)
merged


Out[22]:
{'x': 1, 'y': 2, 'z': 3}

In [23]:
merged["x"]


Out[23]:
1

In [24]:
merged["y"]


Out[24]:
2

In [25]:
merged["z"]


Out[25]:
3

这样也能行得通,但是它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。 同时,如果原字典做了更新,这种改变不会反应到新的合并字典中去。比如:


In [26]:
a["x"] = 13
merged["x"]


Out[26]:
1

ChainMap 使用原来的字典,它自己不创建新的字典。所以它并不会产生上面所说的结果,比如:


In [27]:
a = {"x" : 1, "z" : 3}
b = {"y" : 2, "z" : 4}
merged = ChainMap(a, b)
merged


Out[27]:
ChainMap({'x': 1, 'z': 3}, {'z': 4, 'y': 2})

In [28]:
merged["x"]


Out[28]:
1

In [29]:
a["x"] = 42
merged["x"]


Out[29]:
42